package com.xiguanlezz.cn.client.utils;

import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;

/**
 * @Author：习惯了种种
 * @CreateDate：2024/11/28 09:56
 * @Description：
 */
@Slf4j
public class ConcurrentDiskUtil {

    private static final String READ_ONLY = "r";
    private static final String READ_WRITE = "rw";

    private static final int RETRY_COUNT = 10;
    private static final int SLEEP_BASETIME = 10;


    public static Boolean writeFileContent(File file, String content, String charsetName) throws IOException {

        if (!file.exists() && !file.createNewFile()) {
            return false;
        }
        try (RandomAccessFile raf = new RandomAccessFile(file, READ_WRITE);
             FileChannel channel = raf.getChannel();
             FileLock lock = tryLock(file, channel, false)) {
            byte[] contentBytes = content.getBytes(charsetName);
            ByteBuffer sendBuffer = ByteBuffer.wrap(contentBytes);
            while (sendBuffer.hasRemaining()) {
                channel.write(sendBuffer);
            }
            channel.truncate(contentBytes.length);
        } catch (FileNotFoundException e) {
            throw new IOException("file not exist");
        }
        return true;
    }

    public static String getFileContent(File file, String charsetName) throws IOException {
        try (RandomAccessFile fis = new RandomAccessFile(file, READ_ONLY);
             FileChannel fcin = fis.getChannel();
             FileLock rlock = tryLock(file, fcin, true)) {
            int fileSize = (int) fcin.size();
            ByteBuffer byteBuffer = ByteBuffer.allocate(fileSize);
            fcin.read(byteBuffer);
            byteBuffer.flip();
            return byteBufferToString(byteBuffer, charsetName);
        }
    }

    private static FileLock tryLock(File file, FileChannel channel, boolean shared) throws IOException {
        FileLock result = null;
        int i = 0;
        do {
            try {
                result = channel.tryLock(0L, Long.MAX_VALUE, shared);
            } catch (Exception e) {
                ++i;
                if (i > RETRY_COUNT) {
                    log.error("[NA] lock " + file.getName() + " fail;retryed time: " + i, e);
                    throw new IOException("lock " + file.getAbsolutePath() + " conflict");
                }
                sleep(SLEEP_BASETIME * i);
                log.warn("lock " + file.getName() + " conflict;retry time: " + i);
            }
        } while (null == result);
        return result;
    }

    public static String byteBufferToString(ByteBuffer buffer, String charsetName) throws IOException {
        Charset charset = Charset.forName(charsetName);
        CharsetDecoder decoder = charset.newDecoder();
        CharBuffer charBuffer = decoder.decode(buffer.asReadOnlyBuffer());
        return charBuffer.toString();
    }

    private static void sleep(int time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            log.warn("sleep wrong", e);
            // set the interrupted flag
            Thread.currentThread().interrupt();
        }
    }
}
